//
//  Copyright (C) 1998-2007 J. Andrew McLaughlin
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  kernelLock.c
//

// This header file contains source code for the kernel's standard 
// locking facilities.  These facilities can be used for locking any
// desired resource (i.e. it is not specific to devices, or anything
// in particular).

#include "kernelLock.h"
#include "kernelMisc.h"
#include "kernelMultitasker.h"
#include "kernelPerformanceCounter.h"
#include "kernelProcessorX86.h"
#include "kernelShutdown.h"
#include "kernelSysTimer.h"
#include "kernelError.h"
#include <string.h>

static int pcLocksAcquired = -1;

int kernelLockReleaseIfInvalid(lock *verLock);

/*
	If *Destination == Comperand
		set *Destination = Exchange
		return Comperand
	else
		return *Destination
*/
int _InterlockedCompareExchange(volatile int* Destination, const int Exchange, const int Comperand);
inline int _InterlockedCompareExchange(volatile int* Destination, const int Exchange, const int Comperand)
{
	int retval = Comperand;
	__asm__(
		"lock; cmpxchgl %k[Exchange], %[Destination]"
		: [retval] "+a" (retval)
		: [Destination] "m" (*Destination), [Exchange] "q" (Exchange)
		: "memory");
	return retval;
}


/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//
// Below here, the functions are exported for external use
//
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////


int kernelLockRelease(lock *relLock)
{
	// This function corresponds to the lock function.  It enables a 
	// process to release a resource that it had previously locked.

	int status = 0;
	int currentProcId = 0;
  
	// Make sure the pointer we were given is not NULL
	if (relLock == NULL)
		return (status = ERR_NULLPARAMETER);

	// Get the process Id of the current process
	currentProcId = kernelMultitaskerGetCurrentProcessId();

	// Make sure it's a valid process Id
	if (currentProcId < 0)
		return (currentProcId);

	// Make sure that the current lock, if any, really belongs to this process.
	if (_InterlockedCompareExchange(&relLock->processId, 0, currentProcId) == currentProcId)
		return (status = 0);
	else
		return (status = ERR_NOLOCK);
}


int kernelLockReleaseIfInvalid(lock *verLock)
{
	int status = 0;
	processState tmpState;

	// Make sure the pointer we were given is not NULL
	if (verLock == NULL)
		return (status = ERR_NULLPARAMETER);

	int ProcId = verLock->processId;

	// Make sure there's really a lock here
	if (ProcId == 0)
		return (status = 0);

	// Get the current state of the owning process
	status = kernelMultitaskerGetProcessState(ProcId, &tmpState);

	// Is the process that holds the lock still valid?
	if ((status < 0) || 
	    (tmpState == proc_sleeping) || (tmpState == proc_stopped) ||
	    (tmpState == proc_finished) || (tmpState == proc_zombie))
	{
		if (_InterlockedCompareExchange(&verLock->processId, 0, ProcId) == ProcId)
			return (status = 0);
		else
			return (status = ERR_NOLOCK);
	}
	else
		// It's a valid lock
		return (status = 1);
}

int kernelLockVerify(lock *verLock)
{
	// This function should be used to determine whether a lock is still
	// valid.  This means checking to see whether the locking process still
	// exists, and if so, that it is still viable (i.e. not sleeping, stopped,
	// or zombie.  If the lock is still valid, the function returns 1.  If
	// it is invalid, the function returns 0.

	int status = 0;
	processState tmpState;

	// Make sure the pointer we were given is not NULL
	if (verLock == NULL)
		return (status = ERR_NULLPARAMETER);

	// Make sure there's really a lock here
	if (verLock->processId == 0)
		return (status = 0);

	// Get the current state of the owning process
	status = kernelMultitaskerGetProcessState(verLock->processId, &tmpState);

	// Is the process that holds the lock still valid?
	if ((status < 0) || 
	    (tmpState == proc_sleeping) || (tmpState == proc_stopped) ||
	    (tmpState == proc_finished) || (tmpState == proc_zombie))
	{
		// This process either no longer exists, or else it shouldn't
		// continue holding this lock.
		return (status = 0);
	}
	else
		// It's a valid lock
		return (status = 1);
}


int kernelLockAcquire(lock *getLock, int timeout)
{
	int status = 0;
	int currentProcId = 0;

	extern int kernelProcessingInterrupt;

	// Make sure the pointer we were given is not NULL
	if (getLock == NULL)
		return (status = ERR_NULLPARAMETER);

	// Get the process Id of the current process
	currentProcId = kernelMultitaskerGetCurrentProcessId();
	if (currentProcId < 0)
		return (currentProcId);

	// Check whether the process already has the lock.
	if (getLock->processId == currentProcId)
		return (status = ERR_ALREADY);

	unsigned endTime = 0;
	if (timeout > 0)
	{
		endTime = timeout + kernelSysTimerRead();
	}

	while(1)
	{
		if (_InterlockedCompareExchange(&getLock->processId, currentProcId, 0) == 0)
			break;

		while (getLock->processId != 0)
		{
			// Some other process has locked the resource.  Make sure the process
			// is still alive, and that it is not sleeping, and that it has not
			// become stopped or zombie.  If it has, we will remove the lock given
			// to that process.
			if (kernelLockReleaseIfInvalid(getLock) == 0)
				continue;

			// We didn't get the lock.

			if (kernelProcessingInterrupt)
				// We can't grant this lock to the interrupt service routine
				return (status = ERR_BUSY);

			if (timeout == 0)
				return (status = ERR_BUSY);

			if (timeout > 0)
			{
				if (endTime < kernelSysTimerRead())
					return (status = ERR_TIMEOUT);
			}

			// This process will now have to continue waiting until the lock has 
			// been released or becomes invalid
      
			// Yield this time slice back to the scheduler while the process
			// waits for the lock
			if (kernelMultitaskerYield() < 0)
				return (status = ERR_BUSY);

			// Loop again
		}
	}

	kernelPerformanceCounterIncI(pcLocksAcquired, 1);

	return (status = 0);
}


int kernelLockGet(lock *getLock)
{
	int status;
	
	status = kernelLockAcquire(getLock, -1);

	if (status == ERR_ALREADY)
		status = 0;

	return (status);
}


int kernelLock_Initialize(void)
{
	pcLocksAcquired = kernelPerformanceCounterRegister("kernel.locks_acquired");
	return (0);
}

